/*
 * Copyright (c) zhg2yqq.com Corp.
 * All Rights Reserved.
 */
package com.zhg2yqq.wheels.mvc.protection;

import java.util.Objects;
import java.util.StringJoiner;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.zhg2yqq.wheels.cache.service.ICacheService;
import com.zhg2yqq.wheels.common.exception.SystemException;
import com.zhg2yqq.wheels.common.response.BaseResponseCode;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.extra.servlet.ServletUtil;

/**
 * @author zhg2yqq, 2023年3月22日
 * @version zhg2yqq v1.0
 */
public class DefaultPreventResubmitService implements IPreventResubmitService {
    private Logger log = LoggerFactory.getLogger(DefaultPreventResubmitService.class);
    private static final String PREVENT_RESUBMIT = "PREVENT_RESUBMIT";
    private ICacheService cacheService;

    public DefaultPreventResubmitService(ICacheService cacheService) {
        this.cacheService = cacheService;
    }

    @Override
    public void preResubmit(PreventResubmit preventResubmit, JoinPoint point) throws Throwable {
        String key = buildKey(preventResubmit, point);
        int[] ttl = preventResubmit.ttl();
        Object index = cacheService.getTopicValue(PREVENT_RESUBMIT, key);
        if (index != null) {
            int idx = (int) index;
            if (idx < ttl.length - 1) {
                idx++;
            }
            cacheService.saveTopicCacheWithTtl(PREVENT_RESUBMIT, key, idx, ttl[idx]);
            SystemException ex = SystemException.of(BaseResponseCode.TOO_MANY_REQUESTS);
            if (CharSequenceUtil.isNotEmpty(preventResubmit.message())) {
                ex.setDefaultMessage(preventResubmit.message());
            }
            throw ex;
        }
        cacheService.saveTopicCacheWithTtl(PREVENT_RESUBMIT, key, 0, ttl[0]);
    }

    @Override
    public void removeResubmit(PreventResubmit preventResubmit, JoinPoint point) {
        String key = buildKey(preventResubmit, point);
        cacheService.removeTopicCache(PREVENT_RESUBMIT, key);
    }

    private String buildKey(PreventResubmit preventResubmit, JoinPoint point) {
        StringJoiner key = new StringJoiner(StrPool.UNDERLINE);
        String[] name = preventResubmit.name();
        if (name == null || name.length == 0) {
            key.add(getMethodSign(point));
        } else {
            key.add(name[0]);
        }

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        Assert.notNull(attributes, () -> {
            SystemException ex = SystemException.of(BaseResponseCode.GLOBAL_PARAM_ERROR);
            ex.setDefaultMessage("不支持非API接口方法防重");
            log.error("不支持非API接口方法防重");
            return ex;
        });
        HttpServletRequest request = attributes.getRequest();
        if (preventResubmit.type() == PreventResubmit.PreventResubmitType.IP) {
            String ip = ServletUtil.getClientIP(request);
            if (Validator.isIpv4(ip)) {
                ip = ipv4ToHex(ip);
            } else {
                ip = CharSequenceUtil.removeAll(ip, StrPool.COLON);
            }
            key.add(ip);
        } else if (preventResubmit.type() == PreventResubmit.PreventResubmitType.TOKEN) {
            String nonce = request.getParameter(preventResubmit.argName());
            Assert.notNull(nonce, () -> {
                SystemException ex = SystemException.of(BaseResponseCode.GLOBAL_PARAM_ERROR);
                ex.setDefaultMessage(preventResubmit.argName() + "参数不能为空");
                log.error(preventResubmit.argName() + "参数不能为空");
                return ex;
            });
            key.add(nonce);
        }
        return key.toString();
    }

    private String getMethodSign(JoinPoint point) {
        return String.valueOf(Objects.hash(point.getSignature().toString()));
    }


    private String ipv4ToHex(String ipString) {
        Long[] ip = new Long[4];

        int pos1 = ipString.indexOf(StrPool.DOT);
        int pos2 = ipString.indexOf(StrPool.DOT, pos1 + 1);
        int pos3 = ipString.indexOf(StrPool.DOT, pos2 + 1);
        ip[0] = Long.parseLong(ipString.substring(0, pos1));
        ip[1] = Long.parseLong(ipString.substring(pos1 + 1, pos2));
        ip[2] = Long.parseLong(ipString.substring(pos2 + 1, pos3));
        ip[3] = Long.parseLong(ipString.substring(pos3 + 1));
        return Long.toHexString((ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]);
    }
}
